import { IProtocolHelper, INetworkTips, ProtocolID, Protocol } from "../NetInterface";
import { ByteView } from "../byte/ByteView";
import { Socket } from "./Socket";
import { Event } from "../../../base/event/Event";

export enum SocketLogType {
    Connected = "[C==S]",
    C2S = "[C=>S]",
    S2C = "[S=>C]",
    S2xC = "[S=xC]",
    CxS = "[CxS]"
}

/**
 * Socket节点，实现断线重连、心跳等机制
 */
export class SocketNode {
    protected _socket: Socket = null;
    protected _protocolHelper: IProtocolHelper = null;
    protected _networkTips:INetworkTips = null;
    protected _reconnectTimes:number = 3;
    protected _loger: Function = null;
    private _reconnectCD: number = 3 * 1000;
    private _reconnectCount:number = null;
    private _reconnectHandler:any;
    private _keepAliveHandler:any;
    

    protected _heartbeatID:ProtocolID = 0;
    private _heartbeatCD: number = 5 * 1000;

    public init(socket: Socket, protocolHelper:IProtocolHelper, networkTips: INetworkTips) {
        if(this._socket) return 
        this._socket = socket;
        this._protocolHelper = protocolHelper;
        this._networkTips = networkTips;
    }

    private _addEvent(){
        this._socket.on(Event.OPEN, this, this._onConnected);
        this._socket.on(Event.MESSAGE, this, this._onMessage);
        this._socket.on(Event.ERROR, this, this._onError);
        this._socket.on(Event.CLOSE, this, this._onClosed);
    }

    private _removeEvent(){
        this._socket.off(Event.OPEN, this, this._onConnected);
        this._socket.off(Event.MESSAGE, this, this._onMessage);
        this._socket.off(Event.ERROR, this, this._onError);
        this._socket.off(Event.CLOSE, this, this._onClosed);
    }

    /**
     * 连接服务器
     * @param url 服务器地址 wss://localhost:80
     * @param binaryType BinaryType "arraybuffer" | "blob"
     * @param reconnectTimes 重连次数 -1: 一直尝试重连 0: 不重连 >0: 重连尝试次数
     * @returns 
     */
    public connect(url: string, binaryType: BinaryType="arraybuffer", reconnectTimes: number=3):void {
        this._removeEvent();
        this._addEvent();
        this._networkTips.onConnecting();
        this._reconnectTimes = reconnectTimes;
        this._socket.connectByUrl(url, binaryType);
    }

    /**
     * 发送数据
     * @returns 
     */
    public flush():boolean {
        if (this._socket.connected) {
            this._networkTips.onFlush();
            try {
                this._socket.flush();
                return true;
            }catch (e) {
                return false;
            }
        }
        return false;
    }

    public log(type:SocketLogType, data:Protocol|string):void {
        if(this._loger){
            this._loger(type, typeof data === 'string' ? data : `ID:${data.id} Len:${data.len} Data:${JSON.stringify(data.data)}`);
        }
    }

    /**
     * 关闭连接
     * @param code 
     * @param reason 
     */
    public close(code?:number, reason?:string):void {
        this._cleanupTimeout();
        this._removeEvent();
        this._socket.close(code, reason);
        this.log(SocketLogType.CxS, `${this._socket.url} By Client`);
    }

    protected _onConnected(event:any) {
        this.log(SocketLogType.Connected, this._socket.url);
        this._reconnectCount = null;
        if (this._socket.state == WebSocket.OPEN) {
            this._networkTips.onConnectSuccess();
        }else {
            this._onError(event);
        }
        
    }

    protected _onMessage(data:any): void {
        this._protocolHelper.decode(this._socket.disableInput ? data : this._socket.input);
        this._networkTips.onMessage()
        this._socket.input.clear();
        this._heartbeat();  //重置心跳
    }

    protected _onError(event:any) {
        this._networkTips.onConnectFailed();
        this.log(SocketLogType.CxS, `${this._socket.url}`);
        this._retry();
    }

    protected _onClosed(event:any) {
        this._networkTips.onClosed();
        this.log(SocketLogType.CxS, `${this._socket.url} Code:${event.code} Reason:${event.reason} WasClean:${event.wasClean}`);
        this._retry();
    }

    protected _retry(){
        if(this._reconnectTimes != 0) {
            if(this._reconnectCount == null) {
                this._reconnectCount = this._reconnectTimes == -1 ? 9999 : this._reconnectTimes + 1;
            }
            if(--this._reconnectCount > 0){
                this._reconnectHandler = setTimeout(() => { 
                    this.connect(this._socket.url, this._socket.binaryType, this._reconnectTimes)
                }, this._reconnectCD);
            }
        }
    }

    protected _heartbeat() {
        this._cleanupTimeout();
        this._keepAliveHandler = setTimeout(() => {
            this._protocolHelper.heartbeat();
        }, this._heartbeatCD);
    }

    protected _cleanupTimeout() {
        clearTimeout(this._keepAliveHandler);
        clearTimeout(this._reconnectHandler);
    }

    public set logable(value:boolean) {
        this._loger = value ? console.log : null;
    }

    public get reconnectCD(): number {
        return this._reconnectCD;
    }
    public set reconnectCD(value: number) {
        this._reconnectCD = value;
    }

    public get heartbeatCD(): number {
        return this._heartbeatCD;
    }
    public set heartbeatCD(value: number) {
        this._heartbeatCD = value;
    }

    public get output():ByteView { return this._socket.output};
    public get input():ByteView { return this._socket.input};
}